React Components
React allows you to break a UI down to independent, reusable chunks - components.
Example: The following website could be broken into the following components:
App, which represents your main application and will be the parent of all other components.Navbar, which will be the navigation bar.MainArticle, which will be the component that renders your main content.NewsletterForm, which is a form that lets a user input their email to receive the weekly newsletter.
I. Create Components
1. JSX
React components can be written in both regular JavaScript and JSX, but JSX makes writing components much more intuitive and readable. JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file.
React projects defaults to JSX.
- When using pure JavaScript, we use the React createElement function. This function creates a React element, which is a plain object.
// Without JSX (Pure JavaScript)
function Welcome(props) {
return React.createElement(
'div',
null,
'Hello, ',
props.name
);
}
// With JSX
function Welcome(props) {
return (
<div>
Hello, {props.name}
</div>
);
}
Under the hood, JSX actually gets converted into regular JavaScript (like the first example) during the build process. This conversion is handled by a tool called Babel.
When you create a new React project (whether with Vite or another tool), it automatically includes Babel.
- Babel knows how to handle JSX regardless of the file extension. This is why you'll see both
.jsand.jsxfiles containing JSX code in React projects.- Using
.jsxfor files containing JSX has become a common convention because it makes it immediately clear that the file contains JSX code.
JSX Syntax and Rules
Resources
1. Return a single root element
A component returns a single root element. To return multiple elements in a component, we can either:
- Wrap them in a parent tag. e.g.
<div> - A React fragment
<> </>
- Correct Syntax:
function App() {
// Could replace <></> with <div></div>
return (
<>
<h1>Example h1</h1>
<h2>Example h2</h2>
</>
);
}
- Incorrect Syntax:
function App() {
return (
<h1>Example h1</h1>
<h2>Example h2</h2>
);
}
2. Close ALL tags
In HTML, many tags are self-closing and self-wrapping. In JSX however, we must explicitly close and wrap these tags.
Example:- Correct Syntax:
function App() {
return (
<>
<input />
<li></li>
</>
);
}
- Incorrect Syntax:
function App() {
return (
<>
<input>
<li>
</>
);
}
3. camelCase attributes
JSX turns into JavaScript, and attributes of elements become keys of JavaScript objects, so you can’t use dashes or reserved words such as class.
- Correct Syntax:
function App() {
return (
<div className="container">
<svg>
<circle cx="25" cy="75" r="20" stroke="green" strokeWidth="2" />
</svg>
</div>
);
}
- Incorrect Syntax:
function App() {
return (
<div class="container">
<svg>
<circle cx="25" cy="75" r="20" stroke="green" stroke-width="2" />
</svg>
</div>
);
}
2. React Module System - export & import
React applications are built by combining multiple components, which are typically stored in separate files. To make these components work together, we need to understand JavaScript's module system, which uses import and export statements.
There are two main ways to export components in React:
// Default Export (UserProfile.jsx)
function UserProfile() {
return <div>User Profile Component</div>;
}
export default UserProfile; // Only one default export per file
// Named Export (Button.jsx)
export function PrimaryButton() {
return <button className="primary">Click Me</button>;
}
export function SecondaryButton() {
return <button className="secondary">Click Me</button>;
}
When importing these components, we use corresponding import syntax:
- File extensions (
.jsxor.js) are optional in imports.
// App.jsx
import UserProfile from './UserProfile'; // Default import
import { PrimaryButton, SecondaryButton } from './Button'; // Named imports
function App() {
return (
<div>
<UserProfile />
<PrimaryButton />
<SecondaryButton />
</div>
);
}
The name of the component in React should be capitalized. This is because when JSX is parsed, React uses capitalization to tell the difference between an HTML tag and an instance of a React component. e.g.
Buttoncomponent vs.buttontag.
II. Rendering Components
1. JSX and JavaScript Expressions
Resources:
Curly braces {} in React create a "window" into JavaScript from within JSX, allowing you to embed expressions and dynamic values in your markup.
You can use curly braces in several ways:
1. For displaying variables
function Greeting() {
const name = "Sara";
return <h1>Hello, {name}!</h1>;
}
2. For expressions and calculations
<p>Total: ${price + (price * taxRate)}</p>
3. For component attributes/props:
<img src={imageUrl} alt="Profile" />
<Button disabled={!isVerified}>Edit Profile</Button>
4. For inline styles (using double curly braces):
<p style={{ color: "blue", fontSize: "16px" }}>Styled text</p>
5. For [[#2. Conditional Rendering|conditional rendering]]
<div>
{isLoggedIn ? <WelcomeMessage /> : <LoginButton />}
{hasMessages && <NotificationIcon />}
</div>
2. Rendering Lists
Resources:
a. Basic List Rendering
function App() {
return (
<div>
<h1>Animals: </h1>
<ul>
<li>Lion</li>
<li>Cow</li>
<li>Snake</li>
<li>Lizard</li>
</ul>
</div>
);
}
Instead of manually writing multiple elements like this, we can use JavaScript's map() to render lists. JSX can automatically render arrays of elements
1. Direct Mapping
In this example, both the key and the content of the <li> are using the same value from the animal variable in the map() function.
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<ul>
{animals.map((animal) => <li key={animal}>{animal}</li>)}
</ul>
</div>
);
}
// OUTPUT
// First iteration
<li key="Lion">Lion</li>
// Second iteration
<li key="Cow">Cow</li>
// Third iteration
<li key="Snake">Snake</li>
// Fourth iteration
<li key="Lizard">Lizard</li>
Important Note: When using
map()for rendering, each iteration must return a React element. The arrow function above implicitly returns the<li>element.
2. Separate Variable
Creating a separate variable to hold your mapped elements can make your JSX cleaner and allow you to transform your list before rendering.
In this approach, we process the array with map() first, storing the resulting array of React elements in a variable, and then include that variable in our JSX return statement.
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
const animalsList = animals.map((animal) => <li key={animal}>{animal}</li>);
return (
<div>
<ul>{animalsList}</ul>
</div>
);
}
b. Component-Based List Rendering
Example:
The App component is our top-level component (parent). It contains our source data - an array of animals: ["Lion", "Cow", "Snake", "Lizard"].
- The
Appcomponent passes this array to theListcomponent through what we call a "[[02. React Components#IV. Props |prop]]" (short for property). When we write<List animals={animals} />, we're passing theanimalsarray as as prop named “animals” to theListcomponent.
// top-level component - parent
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"]; // source data
// passes the array to List component
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
The List component receives these props and processes them:
- It creates the
<ul>(unordered list) container - It maps over each animal in the array, creating a
ListItemcomponent for each one. For each animal, it passes two props:key: A special React prop used for optimization (required when mapping no matter the element). This React prop is for React’s internal use only, to identify which items in a list changed, were added, or removed between renders.animal: The actual animal string that we want to display
// This is what props looks like inside the List component
props = {
animals: ["Lion", "Cow", "Snake", "Lizard"]
}
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return <ListItem key={animal} animal={animal} />;
})}
</ul>
);
}
The ListItem component just takes the animal it received as a prop and displays it inside an <li> (list item) tag.
// This is what props looks like inside the ListItem component
props = {
animal: "Lion",
// Note: key is a special prop that React uses internally
// and isn't actually accessible inside props
}
function ListItem(props) {
return <li>{props.animal}</li>
}
2. Conditional Rendering
Resources
a. Ternary Operator
One way to conditionally render an element is with a ternary operator, using a boolean value to decide what to render.
Example:
We are using the String method startsWith to check if the animal starts with the letter L. This method either returns true or false.
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return animal.startsWith("L") ? <li key={animal}>{animal}</li> : null;
})}
</ul>
);
}
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
b.. && operator
When using
&&for conditional rendering, don’t put numbers on the left side.
Example:
If the result of the startsWith function is true, then it returns the second operand, which is the <li> element, and renders it. Otherwise, if the condition is false it just gets ignored.
function List(props) {
return (
<ul>
{props.animals.map((animal) => {
return animal.startsWith("L") && <li key={animal}>{animal}</li>;
})}
</ul>
);
}
function App() {
const animals = ["Lion", "Cow", "Snake", "Lizard"];
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
c. Conditional Statements
We can also use if, if/else, and switch to conditionally render something.
- Check if the
animalsproperty exists - Check if the
animalslength is greater than 0
function List(props) {
if (!props.animals) {
return <div>Loading...</div>;
}
if (props.animals.length === 0) {
return <div>There are no animals in the list!</div>;
}
return (
<ul>
{props.animals.map((animal) => {
return <li key={animal}>{animal}</li>;
})}
</ul>
);
}
function App() {
const animals = [];
return (
<div>
<h1>Animals: </h1>
<List animals={animals} />
</div>
);
}
III. Keys
Resource
Keys are special identifiers that React uses to track individual elements in a list. They are not a normal prop that gets passed down to the component to use, it's a special attribute that React uses internally to keep track of elements, similar to how HTML elements have attributes like id or class.
// SYNTAX
<Component key={keyValue} />
// or
<div key={keyValue} />
function BookList() {
const books = [
{ id: "book_1", title: "React 101" },
{ id: "book_2", title: "JavaScript Basics" }
];
return (
<ul>
{books.map(book => (
// The key is like a sticker on the li element
// React uses this internally, but the ListItem component
// never sees or knows about this key
<li key={book.id}>
{/* Props are passed as data to the component */}
<ListItem title={book.title} />
</li>
))}
</ul>
);
}
When React updates this list, it uses those keys to efficiently track which elements need to change.
1. Rules and Best Practices
Keys must be unique among siblings (but can be repeated in different lists). If you are defining data yourself, it is good practice to assign a unique id to each item. You can use the crypto.randomUUID() function to generate a unique id.
// a list of todos, each todo object has a task and an id
const todos = [
{ task: "mow the yard", id: crypto.randomUUID() },
{ task: "Work on Odin Projects", id: crypto.randomUUID() },
{ task: "feed the cat", id: crypto.randomUUID() },
];
function TodoList() {
return (
<ul>
{todos.map((todo) => (
// here we are using the already generated id as the key.
<li key={todo.id}>{todo.task}</li>
))}
</ul>
);
}
2. Anti-pattern
Keys should never be generated on the fly. Using key={Math.random()} or key={crypto.randomUUID()} while rendering the list defeats the purpose of the key. They’re no longer consistent as now a new key will get created for every render of the list.
→ The key should be inferred from the data itself.
const todos = [
{ task: "mow the yard", id: crypto.randomUUID() },
{ task: "Work on Odin Projects", id: crypto.randomUUID() },
{ task: "feed the cat", id: crypto.randomUUID() },
];
function TodoList() {
return (
<ul>
{todos.map((todo) => (
// DON'T do the following i.e. generating keys during render
<li key={crypto.randomUUID()}>{todo.task}</li>
))}
</ul>
);
}
IV. Props
Props (short for properties) are a way to pass data from parent → child components. They are read-only — child components cannot modify the props they receive.
// Passing props
<ComponentName propName={propValue} />
// Receiving props
function ComponentName(props) {
return <div>{props.propName}</div>;
}
Props can be any data type:
<MyComponent
string="Hello"
number={42}
boolean={true}
array={[1, 2, 3]}
object={{ key: "value" }}
function={() => {}}
/>
1. Accessing Props
Props are always passed as a single object to your component — no matter how many props you pass, they are always bundled into one object. Given:
<Button text="Click Me!" color="blue" fontSize={12} />
React combines them into:
{
text: "Click Me!",
color: "blue",
fontSize: 12
}
You can access them two ways:
- Via
propsparameter:
function Button(props) {
return <button style={{ color: props.color }}>{props.text}</button>;
}
- Via destructuring (more common): You only need to destructure the fields you actually need. Even if the props object has many fields, you only pull out the ones used in that component — the rest are simply ignored. This makes it immediately clear what the component depends on.
// props object coming in:
/* { id: 1,
categoryName: 'food',
maximumBudget: 300,
hasExceeded: false,
totalSpentAmount: 250 }
*/
// only destructure what you need
export const BudgetCard = ({ categoryName, maximumBudget, totalSpentAmount }) => {
return (
<div>
<h4>{categoryName}</h4>
<p>Spent: ${totalSpentAmount}</p>
<p>Budget: ${maximumBudget}</p>
</div>
);
}
// id and hasExceeded exist in the props object but are unused — just ignored
⚠️ Props always arrive as one object, never as separate parameters:
// ❌ wrong — these are treated as two separate parameters, not props
export const BudgetCard = (budget, totalSpentAmount) => { ... }
// ✅ correct — one object, destructure what you need
export const BudgetCard = ({ categoryName, maximumBudget, totalSpentAmount }) => { ... }
2. Default Props
Set default values directly in the destructured parameters:
function Button({ text = "Click Me!", color = "blue", fontSize = 12 }) {
return <button style={{ color, fontSize }}>{text}</button>;
}
<Button /> // uses all defaults
<Button text="Don't Click Me!" color="red" /> // overrides text and color
<Button fontSize={20} /> // overrides only fontSize